#include "PacketConstructer.h"

PacketConstructer::PacketConstructer()
{

}

ASDUStruct PacketConstructer::constructGetBillValueData(QString &addr, QString &groupNo,  const QString &datasType)
{
    ASDUStruct asdu;
    asdu.m_serverType = "15";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = addr;    // addr
    asdu.m_fun = "FE";
    asdu.m_inf = "F1";      // INF = F1 读一个组的全部条目的值或者属性
    asdu.m_rii = "00";  // 由主站端提供，设备只管回一样的
    asdu.m_ngd = "01";

    // 查看压板只有一个 data Part  ，只需要指定组号
    OneDataSetStruct dataSet;
    dataSet.m_ginGroup = groupNo;
    dataSet.m_ginNo = "00";     //  "00"条目是该组全部条目的意思
    dataSet.m_kod = datasType;     // 具体可以是 "0A"，"09","03","02","01"
    dataSet.m_dataNum = "";
    dataSet.m_dataType = "";
    dataSet.m_dataValue = "";
    dataSet.m_dataWidth = "";
    asdu.m_dataSetsList.append(dataSet);    // 所有的需要传输的数据

    return asdu;
}

ASDUStruct PacketConstructer::constructGetBillArea(QString &addr)
{
    ASDUStruct asdu;
    asdu.m_serverType = "15";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = addr;    // addr
    asdu.m_fun = "FE";
    asdu.m_inf = "F1";      // INF = F1 读一个组的全部条目的值或者属性
    asdu.m_rii = "77";  // 由主站端提供，设备只管回一样的
    asdu.m_ngd = "01";

    // 查看压板只有一个 data Part  ，只需要指定组号
    OneDataSetStruct dataSet;
    dataSet.m_ginGroup = "00";       // 系统组 00
    dataSet.m_ginNo = "02";     //  "00"条目是该组全部条目的意思,当前使用的版本区号在 03 条目
    dataSet.m_kod = "01";     // 具体可以是 "0A"，"09","03","02","01"
    dataSet.m_dataNum = "";
    dataSet.m_dataType = "";
    dataSet.m_dataValue = "";
    dataSet.m_dataWidth = "";
    asdu.m_dataSetsList.append(dataSet);    // 所有的需要传输的数据

    return asdu;
}


// 选择1区 0A812A01FEF97701000201030101 ，然后确认执行0A812A01FEFA7701000201030101
ASDUStruct PacketConstructer::constructSelectArea(QString &addr, QString &sysGroupNo, QString & ginNo, QString &billArea)
{
    ASDUStruct asdu;
    asdu.m_serverType = "0A";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = addr;    // addr
    asdu.m_fun = "FE";
    asdu.m_inf = "FA";      // INF = FA 执行写
    asdu.m_rii = "77";  // 由主站端提供，设备只管回一样的
    asdu.m_ngd = "01";

    // 查看压板只有一个 data Part  ，只需要指定组号
    OneDataSetStruct dataSet;
    dataSet.m_ginGroup = sysGroupNo;       // 系统组 00
    dataSet.m_ginNo = Utils::uintStrToHexStr(ginNo, 2);     //  "00"条目是该组全部条目的意思
    dataSet.m_kod = "01";     // 具体可以是 "0A"，"09","03","02","01"
    dataSet.m_dataType = "03";
    dataSet.m_dataWidth = "01";
    dataSet.m_dataNum = "01";
    dataSet.m_dataValue = Utils::uintStrToHexStr(billArea, 2);
    asdu.m_dataSetsList.append(dataSet);    // 所有的需要传输的数据

    return asdu;
}


//send cmd 15 81 2A 01 FE F0 66 01 03 00 10 ，通过这个报文得知 定值组的组号，参数组组号，压板组组号
ASDUStruct PacketConstructer::constructReadAllTitles(QString& applyServiceAddr)
{
    //15 81 2A 01 FE F0 77 01 03 00 10
    ASDUStruct asdu;
    asdu.m_serverType = "15";               //
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = applyServiceAddr;    // 当前南自设备是 1 ，后续再根据设备来判断改变
    asdu.m_fun = "FE";                 // 功能类型，通用分类功能
    asdu.m_inf = "F0";                  // 读全部的组的标题
    asdu.m_rii = "77";                  // 由主站端提供，设备只管回一样的
    asdu.m_ngd = "01";                  // 读标题只有一个数据集合

    // 查看压板只有一个 data Part  ，只需要指定组号
    OneDataSetStruct dataSet;
    dataSet.m_ginGroup = "00";              // 全部组
    dataSet.m_ginNo = "00";                 //  "00"条目是该组全部条目的意思
    dataSet.m_kod = KOD_DESCRIPTER;         // 具体可以是 "0A"，"09","03","02","01"
    asdu.m_dataSetsList.append(dataSet);    // 所有的需要传输的数据

    return asdu;
}



//南自以太网103总召: 07 81 09 01 FF 00 00
QString PacketConstructer::constructGetAllCmd()
{
    QString packet;
    static uint8_t scn = 1;

    if (255 == scn)
    {
        scn = 1;
    }

    packet.append(Utils::number(ASDU7_GETALL, 16));
    packet.append("81");           //vsq 可变结构限定词
    packet.append(Utils::number(COT_S2C_GETALL_START, 16));
    packet.append(ASDU_CPU_ADDR);
    packet.append(Utils::number(FUN_GLB));  // fun = 255   全局服务类型
    packet.append("00");                    //  inf = 1    时间同步或总查询（总召唤）的启动
    packet.append(QString::number(scn,16).toUpper());
    packet.append(QString::number(scn++, 16));

    return packet;
}

ASDUStruct PacketConstructer::constructSelectYBGroupName()
{
    ASDUStruct asdu;
    asdu.m_serverType = "15";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = "01";    // addr
    asdu.m_fun = "FE";
    asdu.m_inf = "F1";      // INF = F1 读一个组的全部条目的值或者属性
    asdu.m_rii = "77";  // 由主站端提供，设备只管回一样的
    asdu.m_ngd = "01";

    // 查看压板只有一个 data Part  ，只需要指定组号
    OneDataSetStruct dataSet;
    dataSet.m_ginGroup = "0E";
    dataSet.m_ginNo = "00";  //  "00"条目是该组全部条目的意思
    dataSet.m_kod = "01";     // 实际值
    asdu.m_dataSetsList.append(dataSet);    // 所有的需要传输的数据

    return asdu;
}

//GROUP=0E KOD = 10（读压板中文名称）
//TCP发送: 15 81 2A 01 FE F1 77 01 0E 00 0A
//TCP接收: 0A 81 2A 01 FE F1 77 03 0E 01 0A 01 0C 01 D4 B6 B7 BD CC F8
//D5 A2 B1 A3 BB A4 0E 02 0A 01 0A 01 B9 FD B5 E7 D1 B9 B1 A3 BB A4 0E 03 0A 01 0C

// 查找压板组的组号 ，定值组组号都一样，改变groupName  即可
QString PacketConstructer::selectYbGroupNo(QString &packet, QString &groupName)
{
    QString ybGroupNo;
    ASDUStruct asdu;

    //这块几个全都是2个字符固定长度，值不变
    asdu.m_serverType =  packet.mid(0,2);
    asdu.m_vsq = packet.mid(2,2);
    asdu.m_cot = packet.mid(4,2);
    asdu.m_addr = packet.mid(6,2);
    asdu.m_fun = packet.mid(8,2);
    asdu.m_inf = packet.mid(10,2);
    asdu.m_rii = packet.mid(12,2);
    asdu.m_ngd = packet.mid(14,2);

    // 获取所有的数据元素集，每一个数据包括：组号、条目号、数据类型、数据宽度、数据个数、数据
    //asdu.m_ginNo = packet.mid(14,2);    // 这个是第一个数据集的组号
    //int dataSetNum = asdu.m_ngd.toUInt();   // 得到数据集的个数

    // 一个数据元素组成 = 组号、条目号、数据类型、数据宽度valueWidth、数据个数、数据
    //  整个数据元素的长度 = 1*2 + 1*2 + 1*2  +  1*2   +    1*2 +  valueWidth*2
    // 每一个数据值的长度不一样导致数据集元素长度不一样，需要一个动态的offset
    QList<OneDataSetStruct> datasets;       // 存放所有的数据段
    // 依次解析出各个数据集元素
    int dataSetIndex = 0;
    OneDataSetStruct dataItem;
    int index;
    int offsetToNext = 0;
    for (index=18; index<packet.size(); index += offsetToNext)  // 第1个数据集元素从16开始
    {
        //依次获取数据元素部分集合
        dataItem.m_ginGroup = packet.mid(index, 2);
        dataItem.m_ginNo = packet.mid(index + 2, 2);    // 数据个数
        dataItem.m_kod = packet.mid(index+4, 2);
        dataItem.m_dataType = packet.mid(index+4, 2);
        dataItem.m_dataWidth =  packet.mid(index+6, 2);
        dataItem.m_dataNum =  packet.mid(index+8, 2);

        //计算数据长度，从 hex16进制转换到10进制
        bool ok;
        uint dataWidth10 =dataItem.m_dataWidth.toUInt(&ok,16);

        dataItem.m_dataValue = packet.mid(index + 10,dataWidth10 * 2);
        datasets.append(dataItem);

        qDebug() << "data width = "  << dataItem.m_dataWidth;
        qDebug() << "value =  "<<dataItem.m_dataValue;

        dataSetIndex += 1;
        offsetToNext = 12 + dataWidth10 * 2;

        // 判断
        qDebug() << "转换后的 str = " << Utils::ChineseStrToHexStr(groupName);
        if (dataItem.m_dataValue == Utils::gbk2Utf8(groupName).toUpper())
        {
            qDebug() << "发现 " << Utils::ChineseStrToHexStr(dataItem.m_dataValue) << "组号 = " << dataItem.m_ginGroup;
            return dataItem.m_ginGroup;
        }
    }

    return "";
}

ASDUStruct PacketConstructer::constructGetBillValueNames(const QString &addr, const QString & groupNo)
{
    ASDUStruct asdu;
    asdu.m_serverType = "15";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = addr;    // addr
    asdu.m_fun = "FE";
    asdu.m_inf = "F1";      // INF = F1 读一个组的全部条目的值或者属性
    asdu.m_rii = "77";  // 由主站端提供，设备只管回一样的
    asdu.m_ngd = "01";

    // 查看压板只有一个 data Part  ，只需要指定组号
    OneDataSetStruct dataSet;
    dataSet.m_ginGroup = groupNo;
    dataSet.m_ginNo = "00";  //  "00"条目是该组全部条目的意思
    dataSet.m_kod = "0A";     // 描述
    dataSet.m_dataNum ="";
    dataSet.m_dataType ="";
    dataSet.m_dataValue ="";
    dataSet.m_dataWidth = "";
    asdu.m_dataSetsList.append(dataSet);    // 所有的需要传输的数据

    return asdu;
}

QString PacketConstructer::asduToQString(ASDUStruct &asdu)
{
    QString packet;
    packet.append(asdu.m_serverType + asdu.m_vsq + asdu.m_cot + asdu.m_addr + asdu.m_fun + asdu.m_inf + asdu.m_rii + asdu.m_ngd);

    // dataSet 部分转 QString
    for(int i=0; i<asdu.m_dataSetsList.count(); i++)
    {
        packet.append(asdu.m_dataSetsList[i].m_ginGroup);
        packet.append(asdu.m_dataSetsList[i].m_ginNo);
        packet.append(asdu.m_dataSetsList[i].m_kod);
        packet.append(asdu.m_dataSetsList[i].m_dataType);
        packet.append(asdu.m_dataSetsList[i].m_dataWidth);
        packet.append(asdu.m_dataSetsList[i].m_dataValue);
    }

    return packet;
}

QByteArray PacketConstructer::asduToQByteArray(ASDUStruct &asdu)
{
    QByteArray packet;
    packet.append(asdu.m_serverType + asdu.m_vsq + asdu.m_cot + asdu.m_addr + asdu.m_fun + asdu.m_inf + asdu.m_rii + asdu.m_ngd);
    for(int i=0; i<asdu.m_dataSetsList.count(); i++)
    {
        packet.append(asdu.m_dataSetsList[i].m_ginGroup);
        packet.append(asdu.m_dataSetsList[i].m_ginNo);
        packet.append(asdu.m_dataSetsList[i].m_kod);
        packet.append(asdu.m_dataSetsList[i].m_dataType);
        packet.append(asdu.m_dataSetsList[i].m_dataWidth);
        packet.append(asdu.m_dataSetsList[i].m_dataValue);
    }

    return packet;
}

// 读指定组的所有条目：描述、值、缺省值、单位、
ASDUStruct PacketConstructer::constructReadGroupValueCmd(QString &groupNo)
{
    ASDUStruct asdu;

    asdu.m_serverType = Utils::number(ASDU_21, 16);
    asdu.m_vsq = "81";
    asdu.m_cot = Utils::number(COT_S2C_GENERAL_READ, 16);
    asdu.m_addr = ASDU_CPU_ADDR;
    asdu.m_rii = m_rii;
    asdu.m_ngd = "01";

    QList<OneDataSetStruct> dataSets;
    OneDataSetStruct dataSet;
    dataSet.m_ginGroup = groupNo;
    dataSet.m_ginNo = "00";         // 指定条目号为 0  , 读取该组所有条目
    dataSet.m_kod = Utils::number(KOD_1, 16);
    dataSets.append(dataSet);
    asdu.m_dataSetsList = dataSets;

    return  asdu;
}

// 带确认的写单个条目
ASDUStruct PacketConstructer::constructModifyWithSure(QString &group, QString &ginNo, QString &dataValue)
{
    ASDUStruct asdu;
    asdu.m_serverType = "0A";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = "01";     //地址默认01
    asdu.m_fun = "FE";
    asdu.m_inf = "F9";     // 这个是 F9 确认和 FA 执行
    asdu.m_rii = "66";
    asdu.m_ngd = "01";     // 数据集的个数

    OneDataSetStruct dataSet;
    dataSet.m_kod = "01";           // kod =1 实际值
    dataSet.m_ginGroup = group;
    dataSet.m_ginNo = ginNo;
    dataSet.m_dataValue = dataValue;

    asdu.m_dataSetsList.append(dataSet);

    return ASDUStruct();
}

// ginlist 和 typelist 按照顺序一一对应，构造一个待写的定值 list,  kod = "01", 定值模板可以提供的数据（定值条目，名称，数值，范围，步长，单位, 数据类型）
QList<OneDataSetStruct> PacketConstructer::constructDataSets(const QString& group, QList<QString>& ginNoList, QList<QString>& typeList, QList<QString>& WidthList, QList<QString>& valueList)
{
    struct OneDataSetStruct dataSet;
    QList<OneDataSetStruct> dataSetList;        // 返回值
    for (int i=1; i<ginNoList.count(); i++)      // 第0个条目表示所有的条目， 实际每组单独计数都是从条目号为1开始
    {
        dataSet.m_ginGroup = group;             // 默认定值组为3
        dataSet.m_ginNo = ginNoList.at(i);
        dataSet.m_kod = "01";                   // 数据描述: 定值的值所有条目都是一样的
        dataSet.m_dataType = typeList.at(i);    // 定值数值的类型，在读取定值组全部定值的时候先已获取到
        dataSet.m_dataWidth = WidthList.at(i);  // 使用现有的数据宽度不变
        dataSet.m_dataNum = 1;                  //定值条目的值固定只有1个值或者属性
        // 定值组的实际值kod=1/kod=2缺省值/kod=3量程 使用的数据类型可能是 0，1,2,3,6,7，9,10 ，宽度不一
        // 量纲kod=9 类型 <1> OS8ASCII
        // 准确度kod = 5， 数据类型 3 UI  宽度 1，数目2
        // 描述 kod = 10, 数据类型 1 OS8ASCII
        if (dataSet.m_dataType == "00")        //无数据
        {
           // dataSet.m_dataWidth = "00";
            dataSet.m_dataValue = "00";
        }
        else if ( dataSet.m_dataType == "01" || dataSet.m_dataType == "02" )  // OS8ASCII ，汉字特殊字符
        {
            //dataSet.m_dataWidth = valueList.at(i).count();      // 计算出当期定值数据的字符个数， ASCII8位码一个字符占用一个字节

            //ascii 转换为 hex
            dataSet.m_dataValue = Utils::ChineseStrToHexStr(valueList.at(i));
        }
        else if (dataSet.m_dataType == "06" || dataSet.m_dataType == "07" )       // 标准短实数也是float类型
        {
            // 将界面上的 数字 转换为 16进制的字符
               dataSet.m_dataValue = Utils::floatStrToHexStr(valueList.at(i));
          //     dataSet.m_dataWidth = "04";
        }
        else if (dataSet.m_dataType == "03")           // 无符号整数
        {
             //将整数字符串转为 hex str
            // dataSet.m_dataValue = valueList.at(i).toUInt(nullptr,16);
             dataSet.m_dataValue =  Utils::uintStrToHexStr(valueList.at(i), dataSet.m_dataWidth.toUInt(nullptr,16));
        }
        else if (dataSet.m_dataType == "04") {
            dataSet.m_dataValue = Utils::intToHexStr(valueList.at(i).toInt(nullptr,10), dataSet.m_dataWidth.toUInt(nullptr,16));
        }
        // 判断中文长度， 数据宽度 = 汉字个数 * 2
        else if( dataSet.m_dataType == "10" )
        {
            //dataSet.m_dataWidth =  Utils::number( dataSet.m_dataValue.size() * 2, 16);      // ASCII 中文编码数据宽度是和汉字个数有关。 一个汉字的size()=1
            //中文转换为 hex str
            dataSet.m_dataValue = Utils::ChineseStrToHexStr(valueList.at(i));
            // uint dataWidth = dataSet.m_dataValue.length()/2;
            //dataSet.m_dataWidth = Utils::uintStrToHexStr(QString::number(dataWidth,10), 2);  //设置数据的宽度
        }
        uint dataWidth = dataSet.m_dataValue.length()/2;
        dataSet.m_dataWidth = Utils::uintStrToHexStr(QString::number(dataWidth,10), 2);  //设置数据的宽度
        dataSetList.append(dataSet);
    }

    return dataSetList;
}


void PacketConstructer::increaseRii(QString& rii)
{
    int riiInt = (rii.toUInt(nullptr,16) + 1);
    rii =  Utils::uintStrToHexStr(QString::number(riiInt,10), 2).toUpper();
    if(rii == "FF")
    {
           rii = "00";
    }

}

// 主站发送 ONF = "F9" , OA 81 28 01 FE F9 77 01 + DATASETs
ASDUStruct PacketConstructer::constructModifyMultilWithSure(const QString& group, QList<QString>& ginNoList, QList<QString>& typeList, QList<QString>& witdthList, QList<QString>& valueList)
{
    ASDUStruct asdu;
    asdu.m_serverType = "0A";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = "01";     //地址默认01
    asdu.m_fun = "FE";
    asdu.m_inf = "F9";     // 这个是 F9 确认和 FA 执行
    asdu.m_rii = "77";

    OneDataSetStruct dataSet;
    dataSet.m_kod = "01";   // 默认我们只修改定值的当前设置值即实际值，单位，步长不管
    asdu.m_dataSetsList = constructDataSets( group, ginNoList, typeList, witdthList, valueList);

    int asduItemsCount = asdu.m_dataSetsList.count();
    asdu.m_ngd = Utils::intToHexByte(asduItemsCount);     // 数据集元素的个数

    return asdu;
}

//主站发送执行写命令只需要修改带确认的写的 INF = "FA"
ASDUStruct PacketConstructer::constructModifyMultilWithExecute(const QString &group, QList<QString> &ginNoList, QList<QString> &typeList, QList<QString>& widthList, QList<QString> &valueList)
{
    ASDUStruct asdu;
    asdu.m_serverType = "0A";
    asdu.m_vsq = "81";
    asdu.m_cot = "2A";
    asdu.m_addr = "01";     //地址默认01
    asdu.m_fun = "FE";
    asdu.m_inf = "FA";     // 这个是 F9 确认和 FA 执行
    asdu.m_rii = "77";

    OneDataSetStruct dataSet;
    dataSet.m_kod = "01";   // 默认我们只修改定值的当前设置值即实际值，单位，步长不管
    asdu.m_dataSetsList = constructDataSets( group, ginNoList, typeList, widthList, valueList);

    int asduItemsCount = asdu.m_dataSetsList.count();
    asdu.m_ngd = Utils::intToHexByte(asduItemsCount);     // 数据集元素的个数

    return asdu;
}

OneDataSetStruct PacketConstructer::constructDataSet(uchar groupNo, uchar ginNo, uchar dataType, uchar dataWidth, uchar dataNum, QString data)
{
    OneDataSetStruct ret;

    ret.m_ginGroup = groupNo;
    ret.m_ginNo = ginNo;
    ret.m_kod = 0x01;
    ret.m_dataType = dataType;
    ret.m_dataWidth = dataWidth;
    ret.m_dataNum = dataNum;
    ret.m_dataValue = data;

    return ret;
}

QByteArray PacketConstructer::constructDataSetByteArray(uchar groupNo, uchar ginNo, uchar dataType, uchar dataWidth, uchar dataNum, QByteArray data)
{
    QByteArray dataset;
    uchar kod = 0x01;
    dataset.append(groupNo + ginNo + kod +  dataType + dataWidth + dataNum );
    dataset += data;
    qDebug() << "合成的数据集元素 set1 = " << dataset;
    return dataset;
}
